////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.series
{

import flash.display.DisplayObject;
import flash.geom.Rectangle;
import mx.charts.HitData;
import mx.charts.chartClasses.BoundedValue;
import mx.charts.chartClasses.DataDescription;
import mx.charts.chartClasses.GraphicsUtilities;
import mx.charts.chartClasses.IStackable;
import mx.charts.chartClasses.InstanceCache;
import mx.charts.chartClasses.LegendData;
import mx.charts.chartClasses.Series;
import mx.charts.chartClasses.StackedSeries;
import mx.charts.renderers.AreaRenderer;
import mx.charts.renderers.BoxItemRenderer;
import mx.charts.series.items.AreaSeriesItem;
import mx.charts.series.renderData.AreaSeriesRenderData;
import mx.charts.styles.HaloDefaults;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.IViewCursor;
import mx.core.ClassFactory;
import mx.core.IDataRenderer;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.mx_internal;
import mx.graphics.IFill;
import mx.graphics.IStroke;
import mx.graphics.SolidColor;
import mx.styles.CSSStyleDeclaration;
import mx.styles.ISimpleStyleClient;
import mx.charts.series.AreaSeries;
import mx.charts.chartClasses.CartesianTransform;
import flash.utils.Dictionary;

use namespace mx_internal;

include "../styles/metadata/FillStrokeStyles.as"
include "../styles/metadata/ItemRendererStyles.as"

/** 
 *  Sets the fill for the area. You can specify either an object implementing the 
 *  IFill interface, or a number representing a solid color value. You can also specify a solid fill using CSS.
 */
[Style(name="areaFill", type="mx.graphics.IFill", inherit="no")]

/**
 *  The class that the series uses to represent the filled area on the chart. This class is instantiated once per series.
 *  Classes used as areaRenderers should implement the IFlexDisplayObject, ISimpleStyleClient, and IDataRenderer 
 *  interfaces. The data property is assigned the 
 *  AreaSeriesRenderData that describes the area data.
 */
[Style(name="areaRenderer", type="mx.core.IFactory", inherit="no")]

/** 
 *  Sets the line style for the area.
 *  You use a Stroke object to define the stroke.
 *  You can specify the itemRenderer in MXML or using styles.  
 */
[Style(name="areaStroke", type="mx.graphics.IStroke", inherit="no")]

/**
 *  Specifies the boundary type for the area.
 *  Possible values are:
 *  
 *  <ul>
 *   <li><code>"curve"</code> - Draws curves for the boundary between data points.</li>
 *  
 *   <li><code>"horizontal"</code> - Draws only the boundary from the x-coordinate of 
 *    the first point to the x-coordinate of the second point at the y-coordinate of 
 *    the second point. Repeats this for each data point.</li>
 *  
 *   <li><code>"reverseStep"</code> - Draws boundaries of the area as horizontal segments. 
 *    At the first data point, draws a vertical boundary line and then a horizontal boundary 
 *    line to the second point, and repeats for each data point.</li>
 *  
 *   <li><code>"segment"</code> - Draws boundaries of the area as connected segments that 
 *    are angled to connect at each data point in the series.</li>
 *  
 *   <li><code>"step"</code> - Draws boundaries of the area as horizontal segments. At the 
 *    first data point, draws a horizontal boundary line and then a vertical boundary 
 *    line to the second point, and repeats for each data point.</li>
 *  
 *   <li><code>"vertical"</code> - Draws only the boundary from the y-coordinate of the first 
 *    point to the y-coordinate of the second point at the x-coordinate of the second point. 
 *    Repeats this for each data point.</li>
 *  </ul>
 *  
 *  @default segment
 */
[Style(name="form", type="String", enumeration="segment,step,reverseStep,vertical,horizontal,curve", inherit="no")]

/** 
 *  Specifies the radius, in pixels, of the chart elements for the data points. This property only applies
 *  if you specify an <code>itemRenderer</code> property.  
 *  You can specify the itemRenderer in MXML or using styles.  
 *  
 *  @default 4 
 */
[Style(name="radius", type="Number", format="Length", inherit="no")]

/**
 *  Defines a data series for an AreaChart control. By default, this class uses the AreaRenderer itemRenderer.
 *  Optionally, you can define a different itemRenderer for the 
 *  data series. The itemRenderer must implement the IDataRenderer interface. 
 *  
 *  @mxml
 *  
 *  <p>The <code>&lt;mx:AreaSeries&gt;</code> tag inherits all the properties
 *  of its parent classes and adds the following properties:</p>
 *  
 *  <pre>
 *  &lt;mx:AreaSeries
 *    <strong>Properties</strong>
 *    minField="null"
 *    stacker="<i>No default</i>"
 *    stackTotals="<i>No default</i>"
 *    xField="null"
 *    yField="null"
 * 
 *    <strong>Styles</strong>
 *    areaFill="<i>IFill</i>"
 *    areaRenderer="<i>areaRenderer</i>"
 *    areaStroke="<i>Stroke</i>"
 *    fill="<i>IFill; no default</i>"
 *    form="<i>segment|curve|horizontal|reverseStep|step|vertical</i>"
 *    itemRenderer="<i>itemRenderer</i>"
 *    legendMarkerRenderer="<i>Defaults to series's itemRenderer</i>"
 *    radius="4"
 *    stroke="<i>IStroke; no default</i>"
 *  /&gt;
 *  </pre>
 *  
 *  @see mx.charts.AreaChart
 *  
 *  @includeExample ../examples/Line_AreaChartExample.mxml
 *  
 */
public class AreaSeries extends Series implements IStackable
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class initialization
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private static var stylesInited:Boolean = initStyles();
		
	/**
	 *  @private
	 */
	private static function initStyles():Boolean
	{
		HaloDefaults.init();

		var areaSeriesStyle:CSSStyleDeclaration =
			HaloDefaults.createSelector("AreaSeries");
		
		areaSeriesStyle.defaultFactory = function():void
		{
			this.areaRenderer = new ClassFactory(mx.charts.renderers.AreaRenderer);
			this.legendMarkerRenderer = new ClassFactory(AreaSeriesLegendMarker);
			this.radius = 4;
			this.areaFill = new SolidColor(0x000000);
			this.stroke = HaloDefaults.pointStroke;
		}

		return true;
	}

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 */
	public function AreaSeries()
	{
		super();

		_instanceCache = new InstanceCache(null, this, 1);
		_instanceCache.creationCallback = applyItemRendererProperties;
	}

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private var _areaRenderer:IFlexDisplayObject;
	
	/**
	 *  @private
	 */
	private var _instanceCache:InstanceCache;
	
	/**
	 *  @private
	 */
	private var _renderData:AreaSeriesRenderData;
	
	/**
	 *  @private
	 */
	private var _stacked:Boolean = false;

	//--------------------------------------------------------------------------
	//
	//  Overridden properties: Series
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  legendData
	//----------------------------------

	/**
	 *  @private
	 */
	override public function get legendData():Array
	{
		var marker:IFlexDisplayObject = null;		
		
		var ld:LegendData = new LegendData();
		ld.element = this;
	
		var markerClass:IFactory = getStyle("legendMarkerRenderer");
		
		if (markerClass)
		{
			marker = markerClass.newInstance();
			if (marker as ISimpleStyleClient)
				(marker as ISimpleStyleClient).styleName = this;
		}
			
		
		ld.marker = marker;
		ld.label = displayName;	

		return [ ld ];
	}

	//----------------------------------
	//  renderData
	//----------------------------------

	/**
	 *  @private
	 */
	override protected function get renderData():Object
	{
		if (!_renderData)
		{
			var renderDataType:Class = this.renderDataType
			var td:AreaSeriesRenderData = new renderDataType(this);
			td.cache = td.filteredCache = [];
			td.radius = 0;
			return td;
		}
					
		_renderData.radius = getStyle("radius");
			
		return _renderData;
	}

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------
	
	//----------------------------------
	//  items
	//----------------------------------

	/**
	 *  @private
	 */
	mx_internal function get items():Array
	{
		return _renderData ? _renderData.filteredCache : null;
	}

	//----------------------------------
	//  itemType
	//----------------------------------
	
	/**
	 *  The subtype of ChartItem used by this series to represent individual items.
	 *  Subclasses can override and return a more specialized class if they need to store additional information in the items
	 */
	protected function get itemType():Class
	{
		return AreaSeriesItem;
	}
	
	//----------------------------------
	//  minField
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the minField property.
	 */
	private var _minField:String = "";

	/**
	 *  @private
	 */
	private var _userMinField:String = "";

    [Inspectable(category="General")]
	
	/**
	 *  Specifies the field of the dataProvider that determines the bottom boundary of the area. 
	 *  If <code>null</code>, the area is based at the range minimum (or maximum, if the field value is negative). 
	 *  
	 *  @default null
	 */
	public function get minField():String
	{
		return _minField;
	};

	/**
	 *  @private
	 */
	public function set minField(value:String):void
	{
		_userMinField = value;
	 	_minField = value;

		dataChanged();
	}

	//----------------------------------
	//  renderDataType
	//----------------------------------

	/**
	 *  The subtype of ChartRenderData used by this series to store all data necessary to render.
	 *  Subclasses can override and return a more specialized class if they need to store additional information for rendering.
	 */
	protected function get renderDataType():Class
	{
		return AreaSeriesRenderData;
	}

    //----------------------------------
	//  sortOnXField
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the sortOnXField property.
	 */
	private var _sortOnXField:Boolean = true;

	/** 
	 *  Requests the line datapoints be sorted from left to right
	 *  before rendering.
	 *
	 *  <p>By default, the LineSeries renders points from left to right.
	 *  Set this property to <code>false</code> to render the items
	 *  in the order they appear in the data provider.</p>
	 *  
	 *  @default true
	 */
	[Inspectable]
	public function get sortOnXField():Boolean
	{
		return _sortOnXField;
	}

	/**
	 *  @private
	 */
	public function set sortOnXField(value:Boolean):void 
	{
		if (_sortOnXField == value)
			return;
		_sortOnXField = value;

		invalidateMapping();
	}

	//----------------------------------
	//  stacker
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the stacker property.
	 */
	private var _stacker:StackedSeries;
	
	/**
	 *  The StackedSeries associated with this AreaSeries.
	 *  The stacker manages the series's stacking behavior.
	 */
	public function get stacker():StackedSeries
	{
		return _stacker;
	}

	/**
	 *  @private
	 */
	public function set stacker(value:StackedSeries):void
	{
		_stacker = value;
	}

	//----------------------------------
	//  upperBoundCache
	//----------------------------------

	/**
	 *  @private
	 */
	mx_internal function get upperBoundCache():Array
	{
		return _renderData.cache;
	}

	//----------------------------------
	//  xField
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the xField property.
	 */
	private var _xField:String = "";

    [Inspectable(category="General")]
	
	/**
	 *  Specifies the field of the data provider that determines the position of the data 
	 *  points on the horizontal axis. If <code>null</code>, the 
	 *  data points are rendered in the order they appear in the data provider.
	 *
	 *  @default null
	 */
	public function get xField():String
	{
		return _xField;
	};
	
	/**
	 *  @private
	 */
	public function set xField(value:String):void
	{
		_xField = value;

		dataChanged();
	}

	//----------------------------------
	//  yField
	//----------------------------------

	/**
	 *  @private
	 *  Storage for the yField property.
	 */
	private var _yField:String = "";

    [Inspectable(category="General")]
	
	/**
	 *  Specifies the field of the data provider that determines the position of the data point on the vertical axis. 
	 *  If <code>null</code>, the AreaSeries assumes the dataProvider 
	 *  is an Array of numbers, and uses the numbers as values for the data points. 
	 *  
	 *  @default null
	 */
	public function get yField():String
	{
		return _yField;
	};

	/**
	 *  @private
	 */
	public function set yField(value:String):void
	{
		_yField = value;

		dataChanged();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden methods: UIComponent
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  @private
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
		super.updateDisplayList(unscaledWidth, unscaledHeight);

		var i:int;
		var v:AreaSeriesItem;
		var elementBounds:Array;
		
		if (!_areaRenderer)
		{
			var asClass:IFactory = getStyle("areaRenderer");
			_areaRenderer = asClass.newInstance();
			if (_areaRenderer is ISimpleStyleClient)
				(_areaRenderer as ISimpleStyleClient).styleName = this;
			addChildAt(_areaRenderer as DisplayObject, 0);
		}

		var renderData:AreaSeriesRenderData =
			transitionRenderData ?
			AreaSeriesRenderData(transitionRenderData) :
			_renderData; 

		var activeRenderCache:Array = renderData.filteredCache;

		var pointRadius:Number;
		
		if (renderData == transitionRenderData &&
			transitionRenderData.elementBounds != null)
		{
			elementBounds = renderData.elementBounds;
			sampleCount = elementBounds.length;
			pointRadius = renderData.radius;
			
			for (i = 0; i < sampleCount; i++)
			{
				rcBounds = elementBounds[i];
				v = activeRenderCache[i];
				v.x = (rcBounds.left + rcBounds.right) / 2;
				v.y = rcBounds.top + pointRadius;
				v.min = rcBounds.bottom;
			}
		}

		_areaRenderer.setActualSize(unscaledWidth, unscaledHeight);
		
		(_areaRenderer as IDataRenderer).data = renderData;		
		
		var sampleCount:int = activeRenderCache.length;		

		var renderCache:Array; 
		var rcBounds:Rectangle;
		
		pointRadius = getStyle("radius");
		if (isNaN(pointRadius))
			pointRadius = 0;
			
		if (pointRadius > 0)
		{
			_instanceCache.factory = getStyle("itemRenderer");
			_instanceCache.count = sampleCount;			
			var instances:Array = _instanceCache.instances;

			var bSetData:Boolean = sampleCount > 0 &&
								   instances[0] is IDataRenderer;

			var rc:Rectangle;
			var inst:IFlexDisplayObject;
			
			if (renderData == transitionRenderData &&
				renderData.elementBounds != null)
			{
				elementBounds = renderData.elementBounds;
				for (i = 0; i < sampleCount; i++)
				{
					inst = instances[i];
					v = activeRenderCache[i];
					v.itemRenderer = inst;
					if (inst != null)
					{
						if (bSetData)
							(inst as IDataRenderer).data = v;
						rc = elementBounds[i];
						inst.move(rc.left, rc.top);
						inst.setActualSize(rc.width, rc.height);
					}
				}
			}
			else
			{
				for (i = 0; i < sampleCount; i++)
				{
					v = activeRenderCache[i];

					inst = instances[i];
					v.itemRenderer = inst;
					if (inst != null)
					{
						if (bSetData)
							(inst as IDataRenderer).data = v;
						rc = new Rectangle(v.x - pointRadius,
										   v.y - pointRadius,
										   2 * pointRadius, 2 * pointRadius);
						inst.move(rc.left, rc.top);
						inst.setActualSize(rc.width, rc.height);
					}
				}
			}
		}
	}

	/**
	 *  @private
	 */
	override public function styleChanged(styleProp:String):void
	{
		super.styleChanged(styleProp);

		var styles:String = "stroke fill";
		if (styleProp == null || styleProp == "" ||
			styles.indexOf(styleProp) != -1)
		{
			legendDataChanged();
		}

		invalidateDisplayList();
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: ChartElement
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override public function describeData(dimension:String,
										  requiredFields:uint):Array
	{
		validateData();

		if (_renderData.cache.length == 0)
			return [];

		var description:DataDescription = new DataDescription();
		description.boundedValues = null;

		if (dimension == CartesianTransform.VERTICAL_AXIS)
		{
			extractMinMax(_renderData.cache, "yNumber", description);
			if (_minField != "")
				extractMinMax(_renderData.cache, "minNumber", description);

			if ((requiredFields & DataDescription.REQUIRED_BOUNDED_VALUES) != 0)
			{
				// for performance reasons, we're assuming people are ok with clipping on the origin. 
				var dataMargin:Number = 0;
				var stroke:IStroke = getStyle("lineStroke");
				if(stroke != null)
					dataMargin = stroke.weight/2;
				var radius:Number = getStyle("radius");
				var renderer:Object = getStyle("itemRenderer");
				if(radius > 0 && renderer != null)
				{
					stroke = getStyle("stroke");
					if(stroke != null)
						radius += stroke.weight/2;
						
					dataMargin = Math.max(radius,dataMargin);
				}
				if(dataMargin > 0)
				{
					description.boundedValues= [];
					description.boundedValues.push(new BoundedValue(description.max,0,dataMargin));
					description.boundedValues.push(new BoundedValue(description.min,dataMargin,0));
				}
			}
		}
		else if (dimension == CartesianTransform.HORIZONTAL_AXIS)
		{
			if (_xField != "")
			{
				if ((requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0)
				{
					// if we need to know the min interval, then we rely on the cache being in order. So we need to sort it if it
					// hasn't already been sorted
					var cache:Array = _renderData.cache;
					if(_sortOnXField == false)
					{
						cache = _renderData.cache.concat();
						cache.sortOn("xNumber",Array.NUMERIC);		
					}
					extractMinMax(cache,"xNumber",description,(0 != (requiredFields & DataDescription.REQUIRED_MIN_INTERVAL)));
				}
				else
				{
					extractMinMax(_renderData.cache,"xNumber",description, (requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0);
				}
			}
			else 
			{
				description.min = _renderData.cache[0].xNumber;
				description.max =
					_renderData.cache[_renderData.cache.length - 1].xNumber;
				if ((requiredFields & DataDescription.REQUIRED_MIN_INTERVAL) != 0)
				{
					extractMinInterval(_renderData.cache,"xNumber",description);
				}
			}
		}
		else
		{
			return [];
		}
			
		return [ description ];
	}

	/**
	 *  @private
	 */
	override public function findDataPoints(x:Number, y:Number,
											sensitivity:Number):Array
	{
		if (interactive == false)
			return [];

		var pr:Number = getStyle("radius");
		var minDist2:Number = pr + sensitivity;
		minDist2 *= minDist2;
		var minItem:AreaSeriesItem;		
		var minIndex:int;
		var pr2:Number = pr * pr;
		
		var len:int = _renderData.filteredCache.length;

		if (len == 0)
			return [];
			
		var low:Number = 0;
		var high:Number = len;
		var cur:Number = Math.floor((low + high) / 2);
		
		while (true)
		{
			var v:AreaSeriesItem = _renderData.filteredCache[cur];			
			
			if (!isNaN(v.yNumber))
			{
				var dist:Number = (v.x - x) * (v.x - x) +
								  (v.y - y) * (v.y - y);
				if (dist <= minDist2)
				{
					minDist2 = dist;
					minItem = v;				
					minIndex = cur;
				}
			}
			
			if (v.x < x)
			{
				low = cur;
				cur = Math.floor((low + high) / 2);
				if (cur == low)
					break;
			}
			else
			{
				high = cur;
				cur = Math.floor((low + high) / 2);
				if (cur == high)
					break;
			}			
		}

		if (minItem != null)
		{
			var hd:HitData = new HitData(createDataID(minItem.index),
										 Math.sqrt(minDist2),
										 minItem.x, minItem.y, minItem);
			hd.contextColor = GraphicsUtilities.colorFromFill(getStyle("areaFill"));
													 
			hd.dataTipFunction = formatDataTip;
			return [ hd ];
		}

		return [];
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: Series
	//
	//--------------------------------------------------------------------------

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------
		
	/**
	 *  @inheritDoc
	 */
	public function stack(stackedXValueDictionary:Dictionary, previousElement:IStackable):Number
	{
		var i:uint = 0;
		var itemClass:Class = itemType;
		var chartItem:AreaSeriesItem;
		var haveYField:Boolean = (_yField != null && _yField != "");
		var haveXField:Boolean = (_xField != null && _xField != "");
		var maxValue:Number = 0;

		var renderDataType:Class = this.renderDataType;
		_renderData= new renderDataType(this);		
		_renderData.cache = [];

		if (cursor != null)
		{
			cursor.seek(CursorBookmark.FIRST);
			while (!cursor.afterLast)
			{
				var dataItem:* = cursor.current;
				_renderData.cache[i] = chartItem = new itemClass(this,dataItem,i);

				var xValue:* = (haveXField)? dataItem[_xField]:i;
				var yValue:* = Number((haveYField)? dataItem[_yField]:dataItem);
				
				var stackedValue:Object = stackedXValueDictionary[xValue];
				if(stackedValue == null)
				{
					stackedValue = 0;
				}
				yValue += stackedValue;
				chartItem.minValue = stackedValue;
				stackedXValueDictionary[xValue] = yValue;
				chartItem.yValue = yValue;
				chartItem.xValue = xValue;
				maxValue = Math.max(maxValue,yValue);
				
				i++;
				cursor.moveNext();				
			}			
		}
		
		invalidateMapping(true);
		invalidateData(false);
		_stacked = (previousElement != null);
		return maxValue;
	}

	/**
	 *  @inheritDoc
	 */
	override protected function invalidateData(invalid:Boolean=true):void
	{
		if(_stacker != null)
			_stacker.invalidateStacking();
		super.invalidateData(invalid);		
	}

	/**
	 *  @inheritDoc
	 */
	override protected function invalidateMapping(invalid:Boolean=true):void
	{
		if(_stacker != null)
			_stacker.invalidateStacking();
		super.invalidateMapping(invalid);		
	}

	/**
	 *  The stack totals for the series.
	 *  @param totals The totals to set.
	 */
	public function set stackTotals(totals:Dictionary):void
	{
		if (totals != null)
		{
			var cache:Array = _renderData.cache;
			var n:int = _renderData.cache.length;
			for (var i:int = 0; i < n; i++)
			{
				var item:AreaSeriesItem = cache[i];
				var total:Number = totals[item.xValue];
				item.yValue = Math.min(100, Number(item.yValue) / total * 100);
				item.minValue =
					Math.min(100, Number(item.minValue) / total * 100);
			}
		}
	}

	/**
	 *  @private
	 */
	override protected function updateData():void
	{
		if(_stacker != null)
		{
			_stacker.stack();
			super.updateData();
			return;
		}	
		_stacked = false;

		var renderDataType:Class = this.renderDataType;
		_renderData = new renderDataType(this);

		_renderData.cache = [];
		
		if (cursor != null)
		{						
			cursor.seek(CursorBookmark.FIRST);
			var i:int = 0;
			var itemType:Class = this.itemType;
			while (!cursor.afterLast)
			{
				_renderData.cache[i] =
					new itemType(this, cursor.current, i);
				i++;
				cursor.moveNext();
			}

			cacheDefaultValues(_yField, _renderData.cache, "yValue");

			cacheIndexValues(_xField, _renderData.cache, "xValue");
			
			if (_minField != "")
				cacheNamedValues(_minField, _renderData.cache, "minValue");				
		}
		
		super.updateData();
	}

	/**
	 *  @private
	 */
	override protected function updateMapping():void
	{
		dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).mapCache(
			_renderData.cache, "xValue", "xNumber", (_xField == ""));
		
		dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).mapCache(
			_renderData.cache, "yValue", "yNumber");
		
		if (_minField != "" || _stacked)		
		{
			dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).mapCache(
				_renderData.cache, "minValue", "minNumber");
		}

		if (_xField != "" && _sortOnXField)
			_renderData.cache.sortOn("xNumber",Array.NUMERIC);		

		super.updateMapping();
	}

	/**
	 *  @private
	 */
	override protected function updateFilter():void
	{
		if (filterData)
		{
			_renderData.filteredCache = _renderData.cache.concat();

			dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).filterCache(
				_renderData.filteredCache, "xNumber", "xFilter");
			stripNaNs(_renderData.filteredCache, "xFilter");
			
			dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).filterCache(
				_renderData.filteredCache, "yNumber", "yFilter");
			stripNaNs(_renderData.filteredCache, "yFilter");
			
			if (_minField != "" || _stacked)		
			{
				dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).filterCache(
					_renderData.filteredCache, "minNumber", "minFilter");
				stripNaNs(_renderData.filteredCache, "minFilter");
			}
		}

		super.updateFilter();
	}
	
	/**
	 *  @private
	 */
	override protected function updateTransform():void
	{
		if (dataProvider == null || _renderData.filteredCache.length == 0)
			return;

		dataTransform.transformCache(
			_renderData.filteredCache, "xNumber", "x", "yNumber", "y");

		var baseVal:Number = dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).baseline;
		var stub:Array = [ { yNumber: baseVal } ];
		dataTransform.transformCache(stub, null, null, "yNumber", "y");
		_renderData.renderedBase = stub[0].y;

		if (_minField != "" || _stacked)		
		{
			dataTransform.transformCache(
				_renderData.filteredCache,null,null,"minNumber","min");
		}

		super.updateTransform();
	}

	/**
	 *  @private
	 */
	override public function getElementBounds(renderData:Object):void
	{
		var cache:Array = renderData.filteredCache;

		var rb:Array = [];
		
		var sampleCount:int = cache.length;		
		
		if (sampleCount)
		{
			var pointRadius:Number = getStyle("radius");
			if (isNaN(pointRadius))
				pointRadius = 0;
			
			var v:AreaSeriesItem = cache[0];
			var maxBounds:Rectangle = new Rectangle(v.x, v.y, 0, 0);
			var i:int;
			var b:Rectangle;
			
			if (minField == "")
			{
				var base:Number = renderData.renderedBase;
				maxBounds.bottom = base;
				
				for (i = 0; i < sampleCount; i++)
				{
					v = cache[i];

					b = new Rectangle(v.x - pointRadius,
									  v.y - pointRadius,
									  2 * pointRadius,
									  base - (v.y - pointRadius));

					maxBounds.left = Math.min(maxBounds.left, b.left);
					maxBounds.top = Math.min(maxBounds.top, b.top);
					maxBounds.right = Math.max(maxBounds.right, b.right);

					rb[i] = b;
				}
			}
			else
			{
				for (i = 0; i < sampleCount; i++)
				{
					v = cache[i];
					
					var top:Number = v.y-pointRadius;
					var bottom:Number = v.min;
					var min:Number = Math.min(top,bottom);
					
					b = new Rectangle(v.x - pointRadius, min,
									  2 * pointRadius,
									  Math.max(top, bottom) - min);
					maxBounds.left = Math.min(maxBounds.left, b.left);
					maxBounds.top = Math.min(maxBounds.top, b.top);
					maxBounds.right = Math.max(maxBounds.right, b.right);
					maxBounds.bottom = Math.max(maxBounds.bottom, b.bottom);
					
					rb[i] = b;
				}
			}
		}
		else
		{
			maxBounds = new Rectangle();
		}

		renderData.elementBounds = rb;
		renderData.bounds =  maxBounds;
	}

	/**
	 *  @private
	 */
	override public function beginInterpolation(sourceRenderData:Object,
												destRenderData:Object):Object
	{
		var interpolatedFields:Object = { x: true, y: true };
		if (minField != "") 
			interpolatedFields["min"] = true;
			
		var idata:Object = initializeInterpolationData(
			sourceRenderData.cache, destRenderData.filteredCache,
			interpolatedFields, itemType,
			{ sourceRenderData: sourceRenderData,
			  destRenderData: destRenderData });
		
		var interpolationRenderData:AreaSeriesRenderData =
			AreaSeriesRenderData(destRenderData.clone());

		interpolationRenderData.filteredCache =
			interpolationRenderData.cache = idata.cache;
			
		transitionRenderData = interpolationRenderData;
		
		return idata;
	}

	/**
	 *  @private
	 */
	override protected function getMissingInterpolationValues(
									sourceProps:Object, srcCache:Array,
									destProps:Object, destCache:Array,
									index:Number, customData:Object):void
	{
		for (var propName:String in sourceProps)
		{
			var src:Number = sourceProps[propName];
			var dst:Number = destProps[propName];

			if (isNaN(src))
			{
				if (srcCache.length == 0)
				{
					sourceProps[propName] = propName == "x" ?
											dst :
											unscaledHeight;
				}
				else
				{
					sourceProps[propName] =
						findClosestValidValue(index,propName,srcCache);
				}
			}

			if (isNaN(dst))
			{
				if (srcCache.length == 0)
				{
					destProps[propName] = propName == "x" ?
										  src :
										  unscaledHeight;
				}
				else
				{
					destProps[propName] =
						findClosestValidValue(index,propName,destCache);
				}
			}
		}
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Customizes the item renderer instances used to represent the chart. This method is called 
	 *  automatically whenever a new item renderer is needed while the chart is being rendered. 
	 *  You can override this method to add your own customization as necessary.
	 *  
	 *	@param	instance	The new item renderer instance that is being created.
	 *	@param	cache		The InstanceCache that is used to manage the item renderer instances.
	 */
	protected function applyItemRendererProperties(instance:DisplayObject,
												   cache:InstanceCache):void
	{
		if (instance is ISimpleStyleClient)
			ISimpleStyleClient(instance).styleName = this;
	}

	/**
	 *  @private
	 */
	private function formatDataTip(hd:HitData):String
	{
		var dt:String = "";
		var n:String = displayName;
		if (n && n != "")
			dt += "<b>"+ n + "</b><BR/>";
		
		var xName:String = dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).displayName;
		if (xName == "")
			xName = xField;
		if (xName != "")
			dt += "<i>" + xName + ": </i>";
			
		var item:AreaSeriesItem = AreaSeriesItem(hd.chartItem);
		var lowItem:AreaSeriesItem = (minField != "") ?
									 item :
									 null;
		dt += dataTransform.getAxis(CartesianTransform.HORIZONTAL_AXIS).formatForScreen(item.xValue) + "\n";

		var yName:String = dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).displayName;

		if (lowItem == null)
		{
			if (yName != "")
				dt += "<i>" + yName + ":</i> ";
			dt += dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).formatForScreen(item.yValue) + "\n";
		}
		else
		{
			if (yName != "")
				dt += "<i>" + yName + " (high):</i> ";
			else
				dt += "<i>high: </i>";
			dt += dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).formatForScreen(item.yValue) + "\n";

			if (yName != "")
				dt += "<i>" + yName + " (low):</i> ";
			else
				dt += "<i>low:</i> ";
			dt += dataTransform.getAxis(CartesianTransform.VERTICAL_AXIS).formatForScreen(lowItem.yValue) + "\n";
		}
		
		return dt;
	}

	/**
	 *  @private
	 */
	private function findClosestValidValue(index:uint, propName:String,
										   cache:Array):Number
	{
		var lastValidIndex:int = index;
		var value:Number;
		
		if (lastValidIndex >= cache.length)
			lastValidIndex = cache.length - 1;
		
		while (lastValidIndex >= 0 && isNaN(cache[lastValidIndex][propName]))
		{
			lastValidIndex--;
		}
		
		if (lastValidIndex >= 0)
			value =  cache[lastValidIndex][propName] 
			
		if (isNaN(value))
		{
			lastValidIndex = index + 1;
			var cachelen:int = cache.length;
			while (lastValidIndex < cachelen &&
				   isNaN(cache[lastValidIndex][propName]))
			{
				lastValidIndex++;
			}
			if (lastValidIndex < cachelen)
			{
				value = cache[lastValidIndex][propName] +
						0.01 * (lastValidIndex - index);
			}
		}
					
		return value + 0.01 * (lastValidIndex - index);
	}
}

}

////////////////////////////////////////////////////////////////////////////////
//
//  Helper class: LineSeriesLegendMarker
//
////////////////////////////////////////////////////////////////////////////////


import flash.display.Graphics;
import mx.charts.series.AreaSeries;
import mx.graphics.IStroke;
import mx.graphics.IFill;
import mx.graphics.Stroke;
import flash.geom.Rectangle;
import mx.charts.chartClasses.GraphicsUtilities;
import mx.graphics.LinearGradientStroke;
import mx.skins.ProgrammaticSkin;

/**
 *  @private
 */
class AreaSeriesLegendMarker extends ProgrammaticSkin
{
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 */
	public function AreaSeriesLegendMarker()
	{
		super();
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
		super.updateDisplayList(unscaledWidth, unscaledHeight);
				
		var fill:IFill = GraphicsUtilities.fillFromStyle(getStyle("areaFill"));
		var stroke:IStroke = getStyle("areaStroke");
				
		var w:Number = stroke ? 0.5 : 0;
		
		var rc:Rectangle = new Rectangle(w, w, width - 2 * w, height - 2 * w);
		
		var g:Graphics = graphics;
		g.clear();		
		g.moveTo(rc.left,rc.top);
		var strokeWeight:Number;
		if (stroke)
		{
			strokeWeight = stroke.weight;
			stroke.weight = 1;
			stroke.apply(g);
		}
		if (fill)
			fill.begin(g,rc);
		g.lineTo(rc.right,rc.top);
		g.lineTo(rc.right,rc.bottom);
		g.lineTo(rc.left,rc.bottom);
		g.lineTo(rc.left,rc.top);
		if (fill)
			fill.end(g);
		if(stroke)
			stroke.weight = strokeWeight;
	}
}
